﻿/*
   Copyright 2011 Softisis

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FOM.Impl;
using FOM.Impl.Conditions;
using FOM.Impl.Converters;
using FOM.Interfaces;
using NUnit.Framework;

namespace TestUnits
{
    [TestFixture]
    public class UsingCacheTest
    {
        
        // test that the same source object will use the same target
        [Test]
        public void TestReUseReference()
        {

        }

        /// <summary>
        /// Test the reuse of a cached reference
        /// </summary>
        [Test]
        public void TestUsingCacheSimpleObjects()
        {
            // create manager
            IManager mngr = new ManagerImpl();

            // source s
            X source=new X();
            source.i = 666;
            source.x1 = source; // cursular reference on itself (sic)
            source.x2=source.x3=new X(); // same refr of nex x for x2 & x3
            source.x2.i = 555; // impact x3 also

            // mapping
            var rootmap = mngr.RegisterMapping<X>("test");

            rootmap.NewInstance(true);
            rootmap.Map(s => s.x1).NewInstance(true);
            rootmap.Map(s => s.x2).NewInstance(true);
            rootmap.Map(s => s.x3).NewInstance(true);

            X res = (X)mngr.ApplyMapping("test", source);

            Assert.IsNotNull(res);
            Assert.IsNotNull(res.x1);
            Assert.IsNotNull(res.x2);
            Assert.IsNotNull(res.x3);

            Assert.IsTrue(object.ReferenceEquals(res, res.x1)); // that was the case of source
            Assert.IsTrue(object.ReferenceEquals(res.x2, res.x3)); // that was the case of source


            // the same mapping, except nocache used for root and x3

            // mapping
            var rootmap2 = mngr.RegisterMapping<X>("test2");

            rootmap2.NewInstance(false); // here is the thing
            rootmap2.Map(s => s.x1).NewInstance(true);
            rootmap2.Map(s => s.x2).NewInstance(true);
            rootmap2.Map(s => s.x3).NewInstance(false); //here is the other thing

            X res2 = (X)mngr.ApplyMapping("test2", source);

            Assert.IsNotNull(res2);
            Assert.IsNotNull(res2.x1);
            Assert.IsNotNull(res2.x2);
            Assert.IsNotNull(res2.x3);

            Assert.IsFalse(object.ReferenceEquals(res2, res2.x1)); // that was the case of source, but not mentionned to engine
            Assert.IsFalse(object.ReferenceEquals(res2.x2, res2.x3)); // that was the case of source, but not mentionned to engine
        }


        /// <summary>
        /// Test the reuse of a cached reference
        /// </summary>
        [Test]
        public void TestUsingCacheDeepObjects()
        {
            // create manager
            IManager mngr = new ManagerImpl();

            // source s
            X source = new X();
            source.i = 666;
            source.x1 = source; // cursular reference on itself (sic)
            source.x2 = source.x3 = new X(); // same refr of nex x for x2 & x3
            source.x2.i = 555; // impacts x3 also

            // now, we add sub object, with cross references
            source.y1=new Y(); // impacts also source object because same reference
            source.y1.x1 = source.x2; // don't cry, please ...

            source.x2.y3 = source.x2.y2 = new Y(); // ... it's for test

            source.x2.y1 = null; // test null ref
            source.x2.y3.x1 = source;

            // mapping
            var rootmap = mngr.RegisterMapping<X>("test");

            rootmap.NewInstance(true); // 1
            rootmap.Map(s => s.x1).NewInstance(true); // 1
            rootmap.Map(s => s.x2).NewInstance(true); // 2
            rootmap.Map(s => s.x3).NewInstance(true); // 2

            rootmap.Map(s => s.y1).NewInstance(true); // 3
            rootmap.Map(s => s.y1.x1).NewInstance(true); // 2
            rootmap.Map(s => s.x2.y2).NewInstance(true); // 4
            rootmap.Map(s => s.x2.y3).NewInstance(true); // 4

            rootmap.Map(s => s.x2.y1).NewInstance(true); // 5 null case


            // to show "data scratching" due to the same parent reference y3 / y2
            rootmap.Map(s => s.x2.y3.x1).NewInstance(true); // no cache use => standalone instance
            rootmap.Map(s => s.x2.y2.x1).NewInstance(false); // no cache use => standalone instance


            X res = (X)mngr.ApplyMapping("test", source);

            Assert.IsNotNull(res);
            Assert.IsNotNull(res.x1);
            Assert.IsNotNull(res.x2);
            Assert.IsNotNull(res.x3);

            Assert.IsTrue(object.ReferenceEquals(res, res.x1)); // that was the case of source
            Assert.IsTrue(object.ReferenceEquals(res.x2, res.x3)); // that was the case of source

            Assert.IsNotNull(res.y1);
            Assert.IsTrue(object.ReferenceEquals(res.y1.x1, res.x2)); // that was the case of source

            Assert.IsTrue(object.ReferenceEquals(res.x2.y3, res.x2.y2)); // that was the case of source

            //Assert.IsNull(res.x2.y1);

            Assert.IsNotNull(res.x2.y3.x1);
            Assert.IsNotNull(res.x2.y2.x1);
            // yes, true! Because the 2 mapping point at a the same parent reference, so the last one is the last taken.
            Assert.IsTrue(object.ReferenceEquals(res.x2.y3.x1, res.x2.y2.x1)); // that was the case of source, but mapping decided other way
        }


        /// <summary>
        /// Simple test on root list object and 1-n association. Reuse cahce and show that ListConverter
        /// has been coded to take the cache in account and this is not magic.
        /// This illustrate how to clone object using NewInstance, but attacking the object list by the
        /// "1" side of the association (or the list side if you prefer). (cd text scheme)
        /// </summary>
        [Test]
        public void TestCachePropagationToListConverterSIMPLE()
        {
            // create manager
            IManager mngr = new ManagerImpl();

            // Mapping is the following (1-*) relationships
            //
            //  List<R>
            //  |   __
            //  `->|r1|(1)-------(*) s1
            //     |  |          /
            //     |r2|(1)------/
            //      --




            var r1 = new R { name = "r1", i = 5 };
            var r2 = new R { name = "r2", i = 6 };

            var s1 = new S { name = "s1", d = 0.95 };
            r1.s = s1;
            r2.s = s1;

            s1.d = 3.66;

            List<R> source = new List<R> { r1, r2 };
            s1.rlist = source;

            var RSubMap = mngr.RegisterMapping<R>().NewInstance()
            .UseConverter(new PropertyCopierConv(true));


            RSubMap.Map(r => r.s).NewInstance(true)
            .UseConverter(new PropertyCopierConv(true))
            .UseCondition(new NotInCacheCond())
            .Map(s => s.rlist).NewInstance(true);

            // Over-mapping to ensure r.s if copied
            RSubMap.Map(r => r.s).NewInstance(true);

            var rootmap = mngr.RegisterMapping<List<R>>("test")
            .NewInstance(true).UseConverter(new ListCrossConv(RSubMap));


            var ret = (List<R>)mngr.ApplyMapping("test", source);

            // testing real cloning
            Assert.IsNotNull(ret);
            Assert.IsNotNull(ret[0]);
            Assert.IsNotNull(ret[1]);
            Assert.IsNotNull(ret[0].s);
            Assert.IsNotNull(ret[1].s);

            Assert.IsFalse(object.ReferenceEquals(ret[0], source[0]));
            Assert.IsFalse(object.ReferenceEquals(ret[1], source[1]));

            // cross test (;-)
            Assert.IsFalse(object.ReferenceEquals(ret[1], source[0]));
            Assert.IsFalse(object.ReferenceEquals(ret[0], source[1]));


            Assert.IsTrue(object.ReferenceEquals(ret[0].s, ret[1].s));
            
            Assert.IsTrue(object.ReferenceEquals(ret[0].s.rlist, ret));
            
        }


        /// <summary>
        /// List converter support the reuse of the cached objects. So, it can be used for complex
        /// 1-n or n-n compositions with relinking. In fact, it clone multiple 1-n relationships,
        /// but diging in the obkect tree from starting from the "1" side of the association  (or the list side if you prefer),
        /// which is naturally a pain in the neck.
        /// </summary>
        [Test]
        public void TestCachePropagationToListConverter()
        {
            // create manager
            IManager mngr = new ManagerImpl();

            // Mapping is the following (1-*) relationships
            //
            //  List<R>
            //  |   __
            //  `->|r1|(1)-------(*) s1 (1)--------(*) t1
            //     |  |          /                 /
            //     |r2|(1)------/                 /
            //     |  |                          /
            //     |r3|(1)-------(*) s2 (1)-----/
            //     |  |
            //     |r4|(1)-------(*) s3 (1)--------(*) t2
            //     ----

            var t1 = new T { name = "t1", c = 'x' };
            var t2 = new T { name = "t2", c = 'y' };

            var s1 = new S { name = "s1", d = 0.95 };
            var s2 = new S { name = "s2", d = 0.96 };
            var s3 = new S { name = "s3", d = 0.97 };

            var r1 = new R { name = "r1", i = 5 };
            var r2 = new R { name = "r2", i = 6 };
            var r3 = new R { name = "r3", i = 7 };
            var r4 = new R { name = "r4", i = 8 };
            
            // build lists and 1-n compositions
            t1.ss.Add(s1); s1.t = t1;
            t1.ss.Add(s2); s2.t = t1;
            t2.ss.Add(s3); s3.t = t2;

            // R-S association
            s1.rlist.Add(r1); r1.s = s1;
            s1.rlist.Add(r2); r2.s = s1;
            s2.rlist.Add(r3); r3.s = s2;
            s3.rlist.Add(r4); r4.s = s3;


            List<R> source = new List<R>{r1,r2,r3,r4};


            // create mapping as a cloning solution with relinking references (ultimate complexity)
            // starting from r list


            // we must create submappings for R elements of root list
            var Rmap = mngr.RegisterMapping<R>( /* name is useless because local use*/)
                .NewInstance(true);// cache R instances

            // then, we define right to left list behavior
            var SMap = mngr.RegisterMapping<S>( /* name is useless because local use*/)
                        .NewInstance(true)
                        .Map(s => s.rlist).NewInstance(true)
                        .UseConverter(new ListCrossConv(Rmap));


            // we go from left to right into the model passing by the 1-1 link
            Rmap.Map(r => r.s).NewInstance(true) // cache S instance
                .Map(s => s.t).NewInstance(true) // cache S.T instance
                .Map(t => t.ss).NewInstance(true) // the S list is cached
                .UseConverter(new ListCrossConv(SMap)); // and we use sub mapping on S


            // top of all
            var rootmap = mngr.RegisterMapping<List<R>>("test")
                .NewInstance().UseConverter(new ListCrossConv(Rmap));


            var ret = mngr.ApplyMapping("test", source);
        }
    }
}
